#include "mp4util.h"




typedef struct {
    char          *name;
    int           (*handler)(mp4u_file_t *mp4,
                                   uint64_t atom_data_size);
} mp4u_atom_handler_t;


#define mp4u_mp4_atom_header(mp4)   (mp4->buffer_pos - 8)
#define mp4u_mp4_atom_data(mp4)     mp4->buffer_pos
#define mp4u_mp4_atom_data_size(t)  (uint64_t) (sizeof(t) - 8)


#define mp4u_mp4_atom_next(mp4, n)                                              \
    mp4->buffer_pos += (size_t) n;                                              \
    mp4->offset += n


#define mp4u_mp4_set_atom_name(p, n1, n2, n3, n4)                               \
    ((u_char *) (p))[4] = n1;                                                   \
    ((u_char *) (p))[5] = n2;                                                   \
    ((u_char *) (p))[6] = n3;                                                   \
    ((u_char *) (p))[7] = n4

#define mp4u_mp4_last_trak(mp4)                                                 \
    &((mp4u_trak_t *) mp4->trak.elts)[mp4->trak.nelts - 1]


static void * _malloc(unsigned int size)      {                                      
        void *p = malloc(size);                                     
        printf("malloc @%p:%u\n",p, size);                        
        return p;
}

static void  _free(void *p)      {                                      
        printf("free :%p\n",p);                        
        free(p);                                     
}

static int mp4u_read_atom(mp4u_file_t *mp4,
           mp4u_atom_handler_t *atom, uint64_t atom_data_size);
static int mp4u_read(mp4u_file_t *mp4, size_t size);
static int mp4u_read_ftyp_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_moov_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_mdat_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static size_t mp4u_update_mdat_atom(mp4u_file_t *mp4,
    off_t start_offset);
static int mp4u_read_mvhd_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_trak_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static void mp4u_update_trak_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static int mp4u_read_cmov_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_tkhd_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_mdia_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static void mp4u_update_mdia_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static int mp4u_read_mdhd_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_hdlr_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_minf_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static void mp4u_update_minf_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static int mp4u_read_dinf_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_vmhd_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_smhd_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_stbl_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static void mp4u_update_stbl_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static int mp4u_read_stsd_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);

/**/
static int mp4u_read_avc1_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_avcC_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_btrt_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_mp4a_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_read_esds_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
/**/

static int mp4u_read_stts_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_update_stts_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static int mp4u_read_stss_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_update_stss_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static int mp4u_read_ctts_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static void mp4u_update_ctts_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static int mp4u_read_stsc_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_update_stsc_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static int mp4u_read_stsz_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_update_stsz_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static int mp4u_read_stco_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_update_stco_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static void mp4u_adjust_stco_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak, int32_t adjustment);
static int mp4u_read_co64_atom(mp4u_file_t *mp4,
    uint64_t atom_data_size);
static int mp4u_update_co64_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak);
static void mp4u_adjust_co64_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak, off_t adjustment);


static int  mp4u_parse_spspps(mp4u_spspps_t  *spspps, u_char *data, uint32_t len);

static mp4u_atom_handler_t  mp4u_atoms[] = {
    { "ftyp", mp4u_read_ftyp_atom },
    { "moov", mp4u_read_moov_atom },
    { "mdat", mp4u_read_mdat_atom },
    { NULL, NULL }
};

static mp4u_atom_handler_t  mp4u_moov_atoms[] = {
    { "mvhd", mp4u_read_mvhd_atom },
    { "trak", mp4u_read_trak_atom },
    { "cmov", mp4u_read_cmov_atom },
    { NULL, NULL }
};

static mp4u_atom_handler_t  mp4u_trak_atoms[] = {
    { "tkhd", mp4u_read_tkhd_atom },
    { "mdia", mp4u_read_mdia_atom },
    { NULL, NULL }
};

static mp4u_atom_handler_t  mp4u_mdia_atoms[] = {
    { "mdhd", mp4u_read_mdhd_atom },
    { "hdlr", mp4u_read_hdlr_atom },
    { "minf", mp4u_read_minf_atom },
    { NULL, NULL }
};

static mp4u_atom_handler_t  mp4u_minf_atoms[] = {
    { "vmhd", mp4u_read_vmhd_atom },
    { "smhd", mp4u_read_smhd_atom },
    { "dinf", mp4u_read_dinf_atom },
    { "stbl", mp4u_read_stbl_atom },
    { NULL, NULL }
};

static mp4u_atom_handler_t  mp4u_stbl_atoms[] = {
    { "stsd", mp4u_read_stsd_atom },
    { "stts", mp4u_read_stts_atom },
    { "stss", mp4u_read_stss_atom },
    { "ctts", mp4u_read_ctts_atom },
    { "stsc", mp4u_read_stsc_atom },
    { "stsz", mp4u_read_stsz_atom },
    { "stco", mp4u_read_stco_atom },
    { "co64", mp4u_read_co64_atom },
    { NULL, NULL }
};


static mp4u_atom_handler_t  mp4u_stsd_atoms[] = {
    { "avc1", mp4u_read_avc1_atom },
    { "mp4a", mp4u_read_mp4a_atom },
    { NULL, NULL }
};

static mp4u_atom_handler_t  mp4u_avc1_atoms[] = {
    { "avcC", mp4u_read_avcC_atom },
    { "btrt", mp4u_read_btrt_atom },
    { NULL, NULL }
};

static mp4u_atom_handler_t  mp4u_mp4a_atoms[] = {
    { "esds", mp4u_read_esds_atom },
    { NULL, NULL }
};


void *
mp4u_array_push(mp4u_array_t *a)
{
    void        *elt;

    if (a->nelts == a->nalloc) {
        mp4u_log_error("array is fulled, may be track more than 2");
        return NULL;
    }

    elt = (u_char *) a->elts + a->size * a->nelts;
    a->nelts++;

    return elt;
}


int 
mp4u_init(mp4u_file_t *mp4, const char *file)
{

   if (mp4 == NULL || file == NULL) return MP4U_ERROR;

   bzero(mp4, sizeof(*mp4));
   mp4u_init_file(&mp4->file, file);
   mp4->end = mp4->file.size; 

   return MP4U_OK;
}

void 
mp4u_free(mp4u_file_t *mp4)
{

    if (mp4->ftyp_atom.buf) {
        _free(mp4->ftyp_atom.buf->pos);
    }

    if (mp4->buffer)
        _free(mp4->buffer);
}

int
mp4u_process(mp4u_file_t *mp4)
{
    int                 rc;

    mp4u_log("mp4 start:%u", mp4->start);

    mp4->buffer_size = BUFFERSIZE;

    rc = mp4u_read_atom(mp4, mp4u_atoms, mp4->end);
    if (rc != MP4U_OK) {
        return rc;
    }

    if (mp4->trak.nelts == 0) {
        mp4u_log_error("ERROR:"
                      "no mp4 trak atoms were found in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    if (mp4->mdat_atom.buf == NULL) {
        mp4u_log_error("ERROR:"
                      "no mp4 mdat atom was found in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    mp4u_log("mp4 process done");

    return MP4U_OK;
}

ssize_t  
mp4u_init_file(file_t *file, const char *filename)
{
   int fd;
   struct stat statBuf;

   if (filename == NULL || file == NULL) return MP4U_ERROR;
   
   fd = open((char*)filename, O_RDONLY, 0644);
   if (fd == MP4U_ERROR){
      mp4u_log_error("ERROR: open %s error(%s)", filename, strerror(errno));
      return MP4U_ERROR;
   }

   if (stat(filename, &statBuf) < 0){
      mp4u_log_error("ERROR: stat %s error(%s)", filename, strerror(errno));
      return MP4U_ERROR;
   } 

   memset(file, 0 , sizeof(file_t));
   file->fd = fd;
   file->name = (char*)filename;
   file->size = statBuf.st_size;

   return MP4U_OK;
}

ssize_t
mp4u_read_file(file_t *file, u_char *buf, size_t size, off_t offset)
{
    ssize_t  n;

    mp4u_log("read: %d, %p, %zu, %lld", file->fd, buf, size, (long long)offset);

#if (NGX_HAVE_PREAD)

    n = pread(file->fd, buf, size, offset);

    if (n == -1) {
        mp4u_log("pread() \"%s\" failed", file->name);
        return MP4U_ERROR;
    }

#else

    if (file->sys_offset != offset) {
        if (lseek(file->fd, offset, SEEK_SET) == -1) {
            mp4u_log("lseek() \"%s\" failed", file->name);
            return MP4U_ERROR;
        }

        file->sys_offset = offset;
    }

    n = read(file->fd, buf, size);

    if (n == -1) {
        mp4u_log("read() \"%s\" failed", file->name);
        return MP4U_ERROR;
    }

    file->sys_offset += n;

#endif

    file->offset += n;

    return n;
}





typedef struct {
    u_char    size[4];
    u_char    name[4];
} mp4u_mp4_atom_header_t;

typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    size64[8];
} mp4u_mp4_atom_header64_t;


static int
mp4u_read_atom(mp4u_file_t *mp4,
    mp4u_atom_handler_t *atom, uint64_t atom_data_size)
{
    off_t        end;
    size_t       atom_header_size;
    u_char      *atom_header, *atom_name;
    uint64_t     atom_size;
    int          rc;
    uint32_t     n;

    end = mp4->offset + atom_data_size;

    while (mp4->offset < end) {

        if (mp4u_read(mp4, sizeof(uint32_t)) != MP4U_OK) {
            return MP4U_ERROR;
        }

        atom_header = mp4->buffer_pos;
        atom_size = mp4u_mp4_get_32value(atom_header);
        atom_header_size = sizeof(mp4u_mp4_atom_header_t);

        if (atom_size == 0) {
            mp4u_log("mp4 atom end");
            return MP4U_OK;
        }

        if (atom_size < sizeof(mp4u_mp4_atom_header_t)) {

            if (atom_size == 1) {

                if (mp4u_read(mp4, sizeof(mp4u_mp4_atom_header64_t))
                    != MP4U_OK)
                {
                    return MP4U_ERROR;
                }

                /* 64-bit atom size */
                atom_header = mp4->buffer_pos;
                atom_size = mp4u_mp4_get_64value(atom_header + 8);
                atom_header_size = sizeof(mp4u_mp4_atom_header64_t);

            } else {
                mp4u_log_error("ERROR:"
                              "\"%s\" mp4 atom is too small:%"PRIu64"",
                              mp4->file.name, atom_size);
                return MP4U_ERROR;
            }
        }

        if (mp4u_read(mp4, sizeof(mp4u_mp4_atom_header_t)) != MP4U_OK) {
            return MP4U_ERROR;
        }

        atom_header = mp4->buffer_pos;
        atom_name = atom_header + sizeof(uint32_t);

        mp4u_log("mp4 atom: %.*s @%lld:%"PRIu64" end:%lld",
                  4, atom_name, (long long)mp4->offset, atom_size, (long long)end);

        if (atom_size > (uint64_t) (MP4U_MAX_OFF_T_VALUE - mp4->offset)
            || mp4->offset + (off_t) atom_size > end)
        {
            mp4u_log_error("ERROR:"
                          "\"%s\" mp4 atom too large:%"PRId64"",
                          mp4->file.name, atom_size);
            return MP4U_ERROR;
        }

        for (n = 0; atom[n].name; n++) {

            if (strncmp((char*)atom_name, (char*)atom[n].name, 4) == 0) {

                mp4u_mp4_atom_next(mp4, atom_header_size);

                rc = atom[n].handler(mp4, atom_size - atom_header_size);
                if (rc != MP4U_OK) {
                    return rc;
                }

                goto next;
            }
        }

        mp4u_mp4_atom_next(mp4, atom_size);

    next:
        continue;
    }

    return MP4U_OK;
}


static int
mp4u_read(mp4u_file_t *mp4, size_t size)
{
    ssize_t  n;

    if (mp4->buffer_pos + size <= mp4->buffer_end) {
        return MP4U_OK;
    }

    if (mp4->offset + (off_t) mp4->buffer_size > mp4->end) {
        mp4->buffer_size = (size_t) (mp4->end - mp4->offset);
    }

    if (mp4->buffer_size < size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 file truncated", mp4->file.name);
        return MP4U_ERROR;
    }

    if (mp4->buffer == NULL) {
        mp4->buffer = _malloc(mp4->buffer_size);
        if (mp4->buffer == NULL) {
            return MP4U_ERROR;
        }

        memset(mp4->buffer, 0, mp4->buffer_size);
        mp4->buffer_start = mp4->buffer;
    }

    n = mp4u_read_file(&mp4->file, mp4->buffer_start, mp4->buffer_size,
                      mp4->offset);

    if (n == MP4U_ERROR) {
        return MP4U_ERROR;
    }

    if ((size_t) n != mp4->buffer_size) {
        mp4u_log_error(" read only %zu of %zu from \"%s\"",
                      n, mp4->buffer_size, mp4->file.name);
        return MP4U_ERROR;
    }

    mp4->buffer_pos = mp4->buffer_start;
    mp4->buffer_end = mp4->buffer_start + mp4->buffer_size;

    return MP4U_OK;
}


static int
mp4u_read_ftyp_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char      *ftyp_atom;
    size_t       atom_size;
    mp4u_buf_t  *atom;

    mp4u_log("mp4 ftyp atom");

    if (atom_data_size > 1024
        || mp4u_mp4_atom_data(mp4) + (size_t) atom_data_size > mp4->buffer_end)
    {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 ftyp atom is too large:%"PRId64"",
                      mp4->file.name, atom_data_size);
        return MP4U_ERROR;
    }

    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;

    ftyp_atom = _malloc(atom_size);
    if (ftyp_atom == NULL) {
        return MP4U_ERROR;
    }
    memset(ftyp_atom, 0, atom_size);

    mp4u_mp4_set_32value(ftyp_atom, atom_size);
    mp4u_mp4_set_atom_name(ftyp_atom, 'f', 't', 'y', 'p');

    /*
     * only moov atom content is guaranteed to be in mp4->buffer
     * during sending response, so ftyp atom content should be copied
     */
    memcpy(ftyp_atom + sizeof(mp4u_mp4_atom_header_t),
               mp4u_mp4_atom_data(mp4), (size_t) atom_data_size);

    atom = &mp4->ftyp_atom_buf;
    atom->pos = ftyp_atom;
    atom->last = ftyp_atom + atom_size;

    mp4->ftyp_atom.buf = atom;
    mp4->ftyp_size = atom_size;
    mp4->content_length = atom_size;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


/*
 * Small excess buffer to process atoms after moov atom, mp4->buffer_start
 * will be set to this buffer part after moov atom processing.
 */
#define MP4_MOOV_BUFFER_EXCESS  (4 * 1024)

static int
mp4u_read_moov_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    int             rc;
    uint32_t        no_mdat;
    mp4u_buf_t      *atom;

    mp4u_log("mp4 moov atom %"PRId64, atom_data_size);

    no_mdat = (mp4->mdat_atom.buf == NULL);

    if (atom_data_size > mp4->buffer_size) {

        if (atom_data_size > MAX_BUFFERSIZE) {
            mp4u_log_error("ERROR:"
                          "\"%s\" mp4 moov atom is too large:%"PRId64", "
                          "you may want to increase mp4_max_buffer_size",
                          mp4->file.name, atom_data_size);
            return MP4U_ERROR;
        }

        _free(mp4->buffer);
        mp4->buffer = NULL;
        mp4->buffer_pos = NULL;
        mp4->buffer_end = NULL;

        mp4->buffer_size = (size_t) atom_data_size
                         + MP4_MOOV_BUFFER_EXCESS * no_mdat;
    }

    if (mp4u_read(mp4, (size_t) atom_data_size) != MP4U_OK) {
        return MP4U_ERROR;
    }

    mp4->trak.elts = &mp4->traks;
    mp4->trak.size = sizeof(mp4u_trak_t);
    mp4->trak.nalloc = 2;

    atom = &mp4->moov_atom_buf;
    atom->pos = mp4->moov_atom_header;
    atom->last = mp4->moov_atom_header + 8;

    mp4->moov_atom.buf = &mp4->moov_atom_buf;

    rc = mp4u_read_atom(mp4, mp4u_moov_atoms, atom_data_size);

    mp4u_log("mp4 moov atom done");

    if (no_mdat) {
        mp4->buffer_start = mp4->buffer_pos;
        mp4->buffer_size = MP4_MOOV_BUFFER_EXCESS;

        if (mp4->buffer_start + mp4->buffer_size > mp4->buffer_end) {
            mp4->buffer = NULL;
            mp4->buffer_pos = NULL;
            mp4->buffer_end = NULL;
        }

    } else {
        /* skip atoms after moov atom */
        mp4->offset = mp4->end;
    }

    return rc;
}


static int
mp4u_read_mdat_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    mp4u_buf_t  *data;

    mp4u_log("mp4 mdat atom %lld", (long long)mp4->offset);

    data = &mp4->mdat_data_buf;
    data->file = &mp4->file;
    data->in_file = 1;
    data->file_pos = mp4->offset;
    data->file_last = mp4->offset + atom_data_size;

    mp4->mdat_atom.buf = &mp4->mdat_atom_buf;
    mp4->mdat_atom.next = &mp4->mdat_data;
    mp4->mdat_data.buf = data;

    if (mp4->trak.nelts) {
        /* skip atoms after mdat atom */
        mp4->offset = mp4->end;

    } else {
        mp4u_mp4_atom_next(mp4, atom_data_size);
    }

    return MP4U_OK;
}


static size_t
mp4u_update_mdat_atom(mp4u_file_t *mp4, off_t start_offset)
{
    off_t       atom_data_size;
    u_char     *atom_header;
    uint32_t    atom_header_size;
    uint64_t    atom_size;
    mp4u_buf_t  *atom;

    atom_data_size = mp4->mdat_data.buf->file_last - start_offset;
    mp4->mdat_data.buf->file_pos = start_offset;

    mp4u_log("mdat new offset @%lld:%lld", (long long)start_offset, (long long)atom_data_size);

    atom_header = mp4->mdat_atom_header;

    if ((uint64_t) atom_data_size > (uint64_t) 0xffffffff) {
        atom_size = 1;
        atom_header_size = sizeof(mp4u_mp4_atom_header64_t);
        mp4u_mp4_set_64value(atom_header + sizeof(mp4u_mp4_atom_header_t),
                            sizeof(mp4u_mp4_atom_header64_t) + atom_data_size);
    } else {
        atom_size = sizeof(mp4u_mp4_atom_header_t) + atom_data_size;
        atom_header_size = sizeof(mp4u_mp4_atom_header_t);
    }

    mp4->content_length += atom_header_size + atom_data_size;

    mp4u_mp4_set_32value(atom_header, atom_size);
    mp4u_mp4_set_atom_name(atom_header, 'm', 'd', 'a', 't');

    atom = &mp4->mdat_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + atom_header_size;

    return atom_header_size;
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    creation_time[4];
    u_char    modification_time[4];
    u_char    timescale[4];
    u_char    duration[4];
    u_char    rate[4];
    u_char    volume[2];
    u_char    reserved[10];
    u_char    matrix[36];
    u_char    preview_time[4];
    u_char    preview_duration[4];
    u_char    poster_time[4];
    u_char    selection_time[4];
    u_char    selection_duration[4];
    u_char    current_time[4];
    u_char    next_track_id[4];
} mp4u_mp4_mvhd_atom_t;

typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    creation_time[8];
    u_char    modification_time[8];
    u_char    timescale[4];
    u_char    duration[8];
    u_char    rate[4];
    u_char    volume[2];
    u_char    reserved[10];
    u_char    matrix[36];
    u_char    preview_time[4];
    u_char    preview_duration[4];
    u_char    poster_time[4];
    u_char    selection_time[4];
    u_char    selection_duration[4];
    u_char    current_time[4];
    u_char    next_track_id[4];
} mp4u_mp4_mvhd64_atom_t;


static int
mp4u_read_mvhd_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char                 *atom_header;
    size_t                  atom_size;
    uint32_t                timescale;
    uint64_t                duration;
    mp4u_buf_t              *atom;
    mp4u_mp4_mvhd_atom_t    *mvhd_atom;
    mp4u_mp4_mvhd64_atom_t  *mvhd64_atom;

    mp4u_log("mp4 mvhd atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    mvhd_atom = (mp4u_mp4_mvhd_atom_t *) atom_header;
    mvhd64_atom = (mp4u_mp4_mvhd64_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(atom_header, 'm', 'v', 'h', 'd');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_mvhd_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 mvhd atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    if (mvhd_atom->version[0] == 0) {
        /* version 0: 32-bit duration */
        timescale = mp4u_mp4_get_32value(mvhd_atom->timescale);
        duration = mp4u_mp4_get_32value(mvhd_atom->duration);

    } else {
        /* version 1: 64-bit duration */

        if (mp4u_mp4_atom_data_size(mp4u_mp4_mvhd64_atom_t) > atom_data_size) {
            mp4u_log_error("ERROR:"
                          "\"%s\" mp4 mvhd atom too small",
                          mp4->file.name);
            return MP4U_ERROR;
        }

        timescale = mp4u_mp4_get_32value(mvhd64_atom->timescale);
        duration = mp4u_mp4_get_64value(mvhd64_atom->duration);
    }

    mp4->timescale = timescale;

    mp4u_log("mvhd timescale:%"PRId32", duration:%"PRId64", time:%.3fs",
                   timescale, duration, (double) duration / timescale);

    duration -= (uint64_t) mp4->start * timescale / 1000;

    mp4u_log("mvhd new duration:%"PRId64", time:%.3fs",
                   duration, (double) duration / timescale);

    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
    mp4u_mp4_set_32value(mvhd_atom->size, atom_size);

    if (mvhd_atom->version[0] == 0) {
        mp4u_mp4_set_32value(mvhd_atom->duration, duration);

    } else {
        mp4u_mp4_set_64value(mvhd64_atom->duration, duration);
    }

    atom = &mp4->mvhd_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + atom_size;

    mp4->mvhd_atom.buf = atom;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_read_trak_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char         *atom_header, *atom_end;
    off_t           atom_file_end;
    int             rc;
    mp4u_buf_t     *atom;
    mp4u_trak_t    *trak;

    mp4u_log("mp4 trak atom");

    trak = mp4u_array_push(&mp4->trak);
    if (trak == NULL) {
        return MP4U_ERROR;
    }

    bzero(trak, sizeof(mp4u_trak_t));

    atom_header = mp4u_mp4_atom_header(mp4);
    mp4u_mp4_set_atom_name(atom_header, 't', 'r', 'a', 'k');

    atom = &trak->trak_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + sizeof(mp4u_mp4_atom_header_t);

    trak->out[MP4_TRAK_ATOM].buf = atom;

    atom_end = mp4->buffer_pos + (size_t) atom_data_size;
    atom_file_end = mp4->offset + atom_data_size;

    rc = mp4u_read_atom(mp4, mp4u_trak_atoms, atom_data_size);

    mp4u_log("mp4 trak atom: %i", rc);

    if (rc == MP4U_DECLINED) {
        /* skip this trak */
        bzero(trak, sizeof(mp4u_trak_t));
        mp4->trak.nelts--;
        mp4->buffer_pos = atom_end;
        mp4->offset = atom_file_end;
        return MP4U_OK;
    }

    return rc;
}


static void
mp4u_update_trak_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    mp4u_buf_t  *atom;

    trak->size += sizeof(mp4u_mp4_atom_header_t);
    atom = &trak->trak_atom_buf;
    mp4u_mp4_set_32value(atom->pos, trak->size);
}


static int
mp4u_read_cmov_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    mp4u_log_error("ERROR:"
                  "\"%s\" mp4 compressed moov atom (cmov) is not supported",
                  mp4->file.name);

    return MP4U_ERROR;
}





static int
mp4u_read_tkhd_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char                 *atom_header;
    size_t                  atom_size;
    uint64_t                duration;
    mp4u_buf_t              *atom;
    mp4u_trak_t    *trak;
    mp4u_mp4_tkhd_atom_t    *tkhd_atom;
    mp4u_mp4_tkhd64_atom_t  *tkhd64_atom;

    mp4u_log("mp4 tkhd atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    tkhd_atom = (mp4u_mp4_tkhd_atom_t *) atom_header;
    tkhd64_atom = (mp4u_mp4_tkhd64_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(tkhd_atom, 't', 'k', 'h', 'd');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_tkhd_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 tkhd atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    if (tkhd_atom->version[0] == 0) {
        /* version 0: 32-bit duration */
        duration = mp4u_mp4_get_32value(tkhd_atom->duration);

    } else {
        /* version 1: 64-bit duration */

        if (mp4u_mp4_atom_data_size(mp4u_mp4_tkhd64_atom_t) > atom_data_size) {
            mp4u_log_error("ERROR:"
                          "\"%s\" mp4 tkhd atom too small",
                          mp4->file.name);
            return MP4U_ERROR;
        }

        duration = mp4u_mp4_get_64value(tkhd64_atom->duration);
    }

    mp4u_log("tkhd duration:%"PRId64", time:%.3fs",
                   duration, (double) duration / mp4->timescale);

    duration -= (uint64_t) mp4->start * mp4->timescale / 1000;

    mp4u_log("tkhd new duration:%"PRId64", time:%.3fs",
                   duration, (double) duration / mp4->timescale);

    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;

    trak = mp4u_mp4_last_trak(mp4);
    trak->tkhd_size = atom_size;

    mp4u_mp4_set_32value(tkhd_atom->size, atom_size);

    if (tkhd_atom->version[0] == 0) {
        mp4u_mp4_set_32value(tkhd_atom->duration, duration);

    } else {
        mp4u_mp4_set_64value(tkhd64_atom->duration, duration);
    }

    atom = &trak->tkhd_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + atom_size;

    trak->out[MP4_TKHD_ATOM].buf = atom;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_read_mdia_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header;
    mp4u_buf_t            *atom;
    mp4u_trak_t  *trak;

    mp4u_log("process mdia atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    mp4u_mp4_set_atom_name(atom_header, 'm', 'd', 'i', 'a');

    trak = mp4u_mp4_last_trak(mp4);

    atom = &trak->mdia_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + sizeof(mp4u_mp4_atom_header_t);

    trak->out[MP4_MDIA_ATOM].buf = atom;

    return mp4u_read_atom(mp4, mp4u_mdia_atoms, atom_data_size);
}


static void
mp4u_update_mdia_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    mp4u_buf_t  *atom;

    trak->size += sizeof(mp4u_mp4_atom_header_t);
    atom = &trak->mdia_atom_buf;
    mp4u_mp4_set_32value(atom->pos, trak->size);
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    creation_time[4];
    u_char    modification_time[4];
    u_char    timescale[4];
    u_char    duration[4];
    u_char    language[2];
    u_char    quality[2];
} mp4u_mp4_mdhd_atom_t;

typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    creation_time[8];
    u_char    modification_time[8];
    u_char    timescale[4];
    u_char    duration[8];
    u_char    language[2];
    u_char    quality[2];
} mp4u_mp4_mdhd64_atom_t;


static int
mp4u_read_mdhd_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char                 *atom_header;
    size_t                  atom_size;
    uint32_t                timescale;
    uint64_t                duration;
    mp4u_buf_t              *atom;
    mp4u_trak_t    *trak;
    mp4u_mp4_mdhd_atom_t    *mdhd_atom;
    mp4u_mp4_mdhd64_atom_t  *mdhd64_atom;

    mp4u_log("mp4 mdhd atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    mdhd_atom = (mp4u_mp4_mdhd_atom_t *) atom_header;
    mdhd64_atom = (mp4u_mp4_mdhd64_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(mdhd_atom, 'm', 'd', 'h', 'd');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_mdhd_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 mdhd atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    if (mdhd_atom->version[0] == 0) {
        /* version 0: everything is 32-bit */
        timescale = mp4u_mp4_get_32value(mdhd_atom->timescale);
        duration = mp4u_mp4_get_32value(mdhd_atom->duration);

    } else {
        /* version 1: 64-bit duration and 32-bit timescale */

        if (mp4u_mp4_atom_data_size(mp4u_mp4_mdhd64_atom_t) > atom_data_size) {
            mp4u_log_error("ERROR:"
                          "\"%s\" mp4 mdhd atom too small",
                          mp4->file.name);
            return MP4U_ERROR;
        }

        timescale = mp4u_mp4_get_32value(mdhd64_atom->timescale);
        duration = mp4u_mp4_get_64value(mdhd64_atom->duration);
    }

    mp4u_log("mdhd timescale:%"PRId32", duration:%"PRId64", time:%"PRId64"s",
                   timescale, duration, (duration/timescale));

    duration -= (uint64_t) mp4->start * timescale / 1000;

    mp4u_log("mdhd new duration:%"PRId64", time:%.3fs",
                   duration, (double) duration / timescale);

    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;

    trak = mp4u_mp4_last_trak(mp4);
    trak->mdhd_size = atom_size;
    trak->timescale = timescale;

    mp4u_mp4_set_32value(mdhd_atom->size, atom_size);

    if (mdhd_atom->version[0] == 0) {
        mp4u_mp4_set_32value(mdhd_atom->duration, duration);

    } else {
        mp4u_mp4_set_64value(mdhd64_atom->duration, duration);
    }

    atom = &trak->mdhd_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + atom_size;

    trak->out[MP4_MDHD_ATOM].buf = atom;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_read_hdlr_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char              *atom_header;
    size_t               atom_size;
    mp4u_buf_t            *atom;
    mp4u_trak_t  *trak;

    mp4u_log("mp4 hdlr atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
    mp4u_mp4_set_32value(atom_header, atom_size);
    mp4u_mp4_set_atom_name(atom_header, 'h', 'd', 'l', 'r');

    trak = mp4u_mp4_last_trak(mp4);

    atom = &trak->hdlr_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + atom_size;

    trak->hdlr_size = atom_size;
    trak->out[MP4_HDLR_ATOM].buf = atom;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_read_minf_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header;
    mp4u_buf_t            *atom;
    mp4u_trak_t  *trak;

    mp4u_log("process minf atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    mp4u_mp4_set_atom_name(atom_header, 'm', 'i', 'n', 'f');

    trak = mp4u_mp4_last_trak(mp4);

    atom = &trak->minf_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + sizeof(mp4u_mp4_atom_header_t);

    trak->out[MP4_MINF_ATOM].buf = atom;

    return mp4u_read_atom(mp4, mp4u_minf_atoms, atom_data_size);
}


static void
mp4u_update_minf_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    mp4u_buf_t  *atom;

    trak->size += sizeof(mp4u_mp4_atom_header_t)
               + trak->vmhd_size
               + trak->smhd_size
               + trak->dinf_size;
    atom = &trak->minf_atom_buf;
    mp4u_mp4_set_32value(atom->pos, trak->size);
}


static int
mp4u_read_vmhd_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char              *atom_header;
    size_t               atom_size;
    mp4u_buf_t            *atom;
    mp4u_trak_t  *trak;

    mp4u_log("mp4 vmhd atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
    mp4u_mp4_set_32value(atom_header, atom_size);
    mp4u_mp4_set_atom_name(atom_header, 'v', 'm', 'h', 'd');

    trak = mp4u_mp4_last_trak(mp4);

    atom = &trak->vmhd_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + atom_size;

    trak->vmhd_size += atom_size;
    trak->out[MP4_VMHD_ATOM].buf = atom;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_read_smhd_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char              *atom_header;
    size_t               atom_size;
    mp4u_buf_t            *atom;
    mp4u_trak_t  *trak;

    mp4u_log("mp4 smhd atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
    mp4u_mp4_set_32value(atom_header, atom_size);
    mp4u_mp4_set_atom_name(atom_header, 's', 'm', 'h', 'd');

    trak = mp4u_mp4_last_trak(mp4);

    atom = &trak->smhd_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + atom_size;

    trak->vmhd_size += atom_size;
    trak->out[MP4_SMHD_ATOM].buf = atom;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_read_dinf_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char              *atom_header;
    size_t               atom_size;
    mp4u_buf_t            *atom;
    mp4u_trak_t  *trak;

    mp4u_log("mp4 dinf atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
    mp4u_mp4_set_32value(atom_header, atom_size);
    mp4u_mp4_set_atom_name(atom_header, 'd', 'i', 'n', 'f');

    trak = mp4u_mp4_last_trak(mp4);

    atom = &trak->dinf_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + atom_size;

    trak->dinf_size += atom_size;
    trak->out[MP4_DINF_ATOM].buf = atom;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_read_stbl_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header;
    mp4u_buf_t            *atom;
    mp4u_trak_t  *trak;

    mp4u_log("process stbl atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    mp4u_mp4_set_atom_name(atom_header, 's', 't', 'b', 'l');

    trak = mp4u_mp4_last_trak(mp4);

    atom = &trak->stbl_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + sizeof(mp4u_mp4_atom_header_t);

    trak->out[MP4_STBL_ATOM].buf = atom;

    return mp4u_read_atom(mp4, mp4u_stbl_atoms, atom_data_size);
}


static void
mp4u_update_stbl_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    mp4u_buf_t  *atom;

    trak->size += sizeof(mp4u_mp4_atom_header_t);
    atom = &trak->stbl_atom_buf;
    mp4u_mp4_set_32value(atom->pos, trak->size);
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    entries[4];

    u_char    media_size[4];
    u_char    media_name[4];
} mp4u_mp4_stsd_atom_t;


static int
mp4u_read_stsd_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header, *atom_table;
    size_t                atom_size;
    mp4u_buf_t            *atom;
    mp4u_mp4_stsd_atom_t  *stsd_atom;
    mp4u_trak_t  *trak;

    /* sample description atom */

    mp4u_log("mp4 stsd atom %"PRId64, atom_data_size);

    atom_header = mp4u_mp4_atom_header(mp4);
    stsd_atom = (mp4u_mp4_stsd_atom_t *) atom_header;
    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
    atom_table = atom_header + atom_size;
    mp4u_mp4_set_32value(stsd_atom->size, atom_size);
    mp4u_mp4_set_atom_name(stsd_atom, 's', 't', 's', 'd');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_stsd_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stsd atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    mp4u_log("stsd entries:%d, media:%*s",
                   mp4u_mp4_get_32value(stsd_atom->entries),
                   4, stsd_atom->media_name);

    trak = mp4u_mp4_last_trak(mp4);

    atom = &trak->stsd_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_table;

    trak->out[MP4_STSD_ATOM].buf = atom;
    trak->size += atom_size;

    mp4u_mp4_atom_next(mp4, 8);
    return mp4u_read_atom(mp4, mp4u_stsd_atoms, atom_data_size-8);
}

static int 
mp4u_read_avc1_atom(mp4u_file_t *mp4, uint64_t atom_data_size) 
{
    u_char                *atom_header;
    size_t                 atom_size, avc1_data_size = sizeof(mp4u_mp4_avc1_atom_t) - 8;
    mp4u_buf_t            *atom;
    mp4u_mp4_avc1_atom_t  *avc1_atom;
    mp4u_trak_t  *trak;

    mp4u_log("process avc1 atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    avc1_atom = (mp4u_mp4_avc1_atom_t *) atom_header;
    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
    mp4u_mp4_set_32value(avc1_atom->size, atom_size);
    mp4u_mp4_set_atom_name(atom_header, 'a', 'v', 'c', '1');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_avc1_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 avc1 atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    mp4u_log("parse %.*s encname:%s", 4, avc1_atom->name, avc1_atom->encname);

    trak = mp4u_mp4_last_trak(mp4);

    atom = &trak->avc1_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_header + atom_size;

    mp4u_mp4_atom_next(mp4, avc1_data_size);
    return mp4u_read_atom(mp4, mp4u_avc1_atoms, atom_data_size - avc1_data_size);
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
  
    u_char    data[1];
/*    u_char    version[1];
    u_char    profile[1];
    u_char    compatibleprofile[1];
    u_char    level[1];
    u_char    nalulength[1];
    u_char    spscount[1];
    u_char    spslength[2];*/
} mp4u_mp4_avcC_atom_t;



static int 
mp4u_read_avcC_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    mp4u_buf_t            *data;
    u_char                *atom_header;
    size_t                 atom_size;
    mp4u_mp4_avcC_atom_t  *avcC_atom;
    mp4u_trak_t           *trak;

    mp4u_log("process avcC atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    avcC_atom = (mp4u_mp4_avcC_atom_t *) atom_header;
    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
    mp4u_mp4_set_32value(avcC_atom->size, atom_size);
    mp4u_mp4_set_atom_name(atom_header, 'a', 'v', 'c', 'C');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_avcC_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 avcC atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    mp4u_log("parse %.*s head:%p  spspps:%p  atom_data_size:%"PRId64, 
                   4, avcC_atom->name, atom_header, &avcC_atom->data,  atom_data_size);

    trak = mp4u_mp4_last_trak(mp4);
    trak->avcC_size = atom_size;

    data = &trak->avcC_data_buf;
    data->pos = avcC_atom->data;
    data->last = avcC_atom->data + atom_data_size;

    //mp4u_parse_spspps(&trak->spspps, avcC_atom->data, atom_data_size);

    mp4u_mp4_atom_next(mp4, atom_data_size);
    return MP4U_OK;
}

static int 
mp4u_read_btrt_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    mp4u_log("process btrt atom");
    mp4u_mp4_atom_next(mp4, atom_data_size);
    return MP4U_OK;
}


static int 
mp4u_read_mp4a_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    mp4u_buf_t            *data;  
    u_char                *atom_header;
    size_t                 atom_size, mp4a_data_size = sizeof(mp4u_mp4_mp4a_atom_t) - 8;
    mp4u_mp4_mp4a_atom_t  *mp4a_atom;
    mp4u_trak_t           *trak;

    mp4u_log("process mp4a atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    mp4a_atom = (mp4u_mp4_mp4a_atom_t *) atom_header;
    atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
    mp4u_mp4_set_32value(mp4a_atom->size, atom_size);
    mp4u_mp4_set_atom_name(atom_header, 'm', 'p', '4', 'a');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_mp4a_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 mp4a atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    mp4u_log("parse %.*s channels:%d samplesize:%d samplerate:%d", 
              4, mp4a_atom->name, mp4u_mp4_get_16value(mp4a_atom->channels),
              mp4u_mp4_get_16value(mp4a_atom->samplesize), mp4u_mp4_get_32value(mp4a_atom->samplerate));
    trak = mp4u_mp4_last_trak(mp4);
    trak->mp4a_size = atom_size;

    data = &trak->mp4a_atom_buf;
    data->pos = (u_char*)atom_header;
    data->last = data->pos + 8  + atom_data_size;

    mp4u_mp4_atom_next(mp4, mp4a_data_size);
    return mp4u_read_atom(mp4, mp4u_mp4a_atoms, atom_data_size - mp4a_data_size);
}

static int 
mp4u_read_esds_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    mp4u_buf_t            *data;  
    u_char                *atom_header;
    mp4u_trak_t           *trak;

    mp4u_log("process esds atom");

    atom_header = mp4u_mp4_atom_header(mp4);

    trak = mp4u_mp4_last_trak(mp4);

    data = &trak->esds_data_buf;
    data->pos = (u_char*)(atom_header + 8);
    data->last = data->pos  + atom_data_size;

    mp4u_mp4_atom_next(mp4, atom_data_size);
    return MP4U_OK;
}

typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    entries[4];
} mp4u_mp4_stts_atom_t;


static int
mp4u_read_stts_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header, *atom_table, *atom_end;
    uint32_t              entries;
    mp4u_buf_t            *atom, *data;
    mp4u_mp4_stts_atom_t  *stts_atom;
    mp4u_trak_t  *trak;

    /* time-to-sample atom */

    mp4u_log("mp4 stts atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    stts_atom = (mp4u_mp4_stts_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(stts_atom, 's', 't', 't', 's');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_stts_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stts atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    entries = mp4u_mp4_get_32value(stts_atom->entries);

    mp4u_log("mp4 time-to-sample entries:%uD", entries);

    if (mp4u_mp4_atom_data_size(mp4u_mp4_stts_atom_t)
        + entries * sizeof(mp4u_mp4_stts_entry_t) > atom_data_size)
    {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stts atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    atom_table = atom_header + sizeof(mp4u_mp4_stts_atom_t);
    atom_end = atom_table + entries * sizeof(mp4u_mp4_stts_entry_t);

    trak = mp4u_mp4_last_trak(mp4);
    trak->time_to_sample_entries = entries;

    atom = &trak->stts_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_table;

    data = &trak->stts_data_buf;
    data->pos = atom_table;
    data->last = atom_end;

    trak->out[MP4_STTS_ATOM].buf = atom;
    trak->out[MP4_STTS_DATA].buf = data;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_update_stts_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    size_t                 atom_size;
    uint32_t               entries, count, duration;
    uint64_t               start_time;
    mp4u_buf_t             *atom, *data;
    uint32_t               start_sample;
    mp4u_mp4_stts_atom_t   *stts_atom;
    mp4u_mp4_stts_entry_t  *entry, *end;

    /*
     * mdia.minf.stbl.stts updating requires trak->timescale
     * from mdia.mdhd atom which may reside after mdia.minf
     */

    mp4u_log("mp4 stts atom update");

    data = trak->out[MP4_STTS_DATA].buf;

    if (data == NULL) {
        mp4u_log_error("ERROR:"
                      "no mp4 stts atoms were found in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    entries = trak->time_to_sample_entries;
    start_time = (uint64_t) mp4->start * trak->timescale / 1000;

    mp4u_log("time-to-sample start_time:%"PRId64"", start_time);

    start_sample = 0;
    entry = (mp4u_mp4_stts_entry_t *) data->pos;
    end = (mp4u_mp4_stts_entry_t *) data->last;

    while (entry < end) {
        count = mp4u_mp4_get_32value(entry->count);
        duration = mp4u_mp4_get_32value(entry->duration);

        mp4u_log("count:%"PRId32", duration:%"PRId32"", count, duration);

        if (start_time < (uint64_t) count * duration) {
            start_sample += (uint32_t) (start_time / duration);
            count -= (uint32_t) (start_time / duration);
            mp4u_mp4_set_32value(entry->count, count);
            goto found;
        }

        start_sample += count;
        start_time -= count * duration;
        entries--;
        entry++;
    }

    mp4u_log_error("ERROR:"
                  "start time is out mp4 stts samples in \"%s\"",
                  mp4->file.name);

    return MP4U_ERROR;

found:

    mp4u_log("start_sample:%"PRId32", new count:%"PRId32"", start_sample, count);

    trak->start_sample = start_sample;

    data->pos = (u_char *) entry;
    atom_size = sizeof(mp4u_mp4_stts_atom_t) + (data->last - data->pos);
    trak->size += atom_size;

    atom = trak->out[MP4_STTS_ATOM].buf;
    stts_atom = (mp4u_mp4_stts_atom_t *) atom->pos;
    mp4u_mp4_set_32value(stts_atom->size, atom_size);
    mp4u_mp4_set_32value(stts_atom->entries, entries);

    return MP4U_OK;
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    entries[4];
} mp4u_stss_atom_t;


static int
mp4u_read_stss_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char                    *atom_header, *atom_table, *atom_end;
    uint32_t                   entries;
    mp4u_buf_t                 *atom, *data;
    mp4u_trak_t       *trak;
    mp4u_stss_atom_t  *stss_atom;

    /* sync samples atom */

    mp4u_log("mp4 stss atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    stss_atom = (mp4u_stss_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(stss_atom, 's', 't', 's', 's');

    if (mp4u_mp4_atom_data_size(mp4u_stss_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stss atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    entries = mp4u_mp4_get_32value(stss_atom->entries);

    mp4u_log("sync sample entries:%uD", entries);

    trak = mp4u_mp4_last_trak(mp4);
    trak->sync_samples_entries = entries;

    atom_table = atom_header + sizeof(mp4u_stss_atom_t);

    atom = &trak->stss_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_table;

    if (mp4u_mp4_atom_data_size(mp4u_stss_atom_t)
        + entries * sizeof(uint32_t) > atom_data_size)
    {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stss atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    atom_end = atom_table + entries * sizeof(uint32_t);

    data = &trak->stss_data_buf;
    data->pos = atom_table;
    data->last = atom_end;

    trak->out[MP4_STSS_ATOM].buf = atom;
    trak->out[MP4_STSS_DATA].buf = data;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_update_stss_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    size_t                     atom_size;
    uint32_t                   entries, sample, start_sample, *entry, *end;
    mp4u_buf_t                 *atom, *data;
    mp4u_stss_atom_t  *stss_atom;

    /*
     * mdia.minf.stbl.stss updating requires trak->start_sample
     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
     * atom which may reside after mdia.minf
     */

    mp4u_log("mp4 stss atom update");

    data = trak->out[MP4_STSS_DATA].buf;

    if (data == NULL) {
        return MP4U_OK;
    }

    /* sync samples starts from 1 */
    start_sample = trak->start_sample + 1;
    entries = trak->sync_samples_entries;

    entry = (uint32_t *) data->pos;
    end = (uint32_t *) data->last;

    while (entry < end) {
        sample = mp4u_mp4_get_32value(entry);

        mp4u_log("start:%"PRId32", sync:%"PRId32"", start_sample, sample);

        if (sample >= start_sample) {
            goto found;
        }

        entries--;
        entry++;
    }

    mp4u_log_error("ERROR:"
                  "start sample is out of mp4 stss atom in \"%s\"",
                  mp4->file.name);

    return MP4U_ERROR;

found:

    data->pos = (u_char *) entry;

    start_sample = trak->start_sample;

    while (entry < end) {
        sample = mp4u_mp4_get_32value(entry);
        sample -= start_sample;
        mp4u_mp4_set_32value(entry, sample);
        entry++;
    }

    atom_size = sizeof(mp4u_stss_atom_t) + (data->last - data->pos);
    trak->size += atom_size;

    atom = trak->out[MP4_STSS_ATOM].buf;
    stss_atom = (mp4u_stss_atom_t *) atom->pos;

    mp4u_mp4_set_32value(stss_atom->size, atom_size);
    mp4u_mp4_set_32value(stss_atom->entries, entries);

    return MP4U_OK;
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    entries[4];
} mp4u_mp4_ctts_atom_t;

typedef struct {
    u_char    count[4];
    u_char    offset[4];
} mp4u_mp4_ctts_entry_t;


static int
mp4u_read_ctts_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header, *atom_table, *atom_end;
    uint32_t              entries;
    mp4u_buf_t            *atom, *data;
    mp4u_mp4_ctts_atom_t  *ctts_atom;
    mp4u_trak_t  *trak;

    /* composition offsets atom */

    mp4u_log("mp4 ctts atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    ctts_atom = (mp4u_mp4_ctts_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(ctts_atom, 'c', 't', 't', 's');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_ctts_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 ctts atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    entries = mp4u_mp4_get_32value(ctts_atom->entries);

    mp4u_log("composition offset entries:%uD", entries);

    trak = mp4u_mp4_last_trak(mp4);
    trak->composition_offset_entries = entries;

    atom_table = atom_header + sizeof(mp4u_mp4_ctts_atom_t);

    atom = &trak->ctts_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_table;

    if (mp4u_mp4_atom_data_size(mp4u_mp4_ctts_atom_t)
        + entries * sizeof(mp4u_mp4_ctts_entry_t) > atom_data_size)
    {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 ctts atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    atom_end = atom_table + entries * sizeof(mp4u_mp4_ctts_entry_t);

    data = &trak->ctts_data_buf;
    data->pos = atom_table;
    data->last = atom_end;

    trak->out[MP4_CTTS_ATOM].buf = atom;
    trak->out[MP4_CTTS_DATA].buf = data;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static void
mp4u_update_ctts_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    size_t                 atom_size;
    uint32_t               entries, count, start_sample;
    mp4u_buf_t             *atom, *data;
    mp4u_mp4_ctts_atom_t   *ctts_atom;
    mp4u_mp4_ctts_entry_t  *entry, *end;

    /*
     * mdia.minf.stbl.ctts updating requires trak->start_sample
     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
     * atom which may reside after mdia.minf
     */

    mp4u_log("mp4 ctts atom update");

    data = trak->out[MP4_CTTS_DATA].buf;

    if (data == NULL) {
        return;
    }

    /* sync samples starts from 1 */
    start_sample = trak->start_sample + 1;
    entries = trak->composition_offset_entries;
    entry = (mp4u_mp4_ctts_entry_t *) data->pos;
    end = (mp4u_mp4_ctts_entry_t *) data->last;

    while (entry < end) {
        count = mp4u_mp4_get_32value(entry->count);

        mp4u_log("start:%"PRId32", count:%"PRId32", offset:%"PRId32"",
                       start_sample, count, mp4u_mp4_get_32value(entry->offset));

         if (start_sample <= count) {
             count -= (start_sample - 1);
             mp4u_mp4_set_32value(entry->count, count);
             goto found;
         }

         start_sample -= count;
         entries--;
         entry++;
    }

    trak->out[MP4_CTTS_ATOM].buf = NULL;
    trak->out[MP4_CTTS_DATA].buf = NULL;

    return;

found:

    data->pos = (u_char *) entry;
    atom_size = sizeof(mp4u_mp4_ctts_atom_t) + (data->last - data->pos);
    trak->size += atom_size;

    atom = trak->out[MP4_CTTS_ATOM].buf;
    ctts_atom = (mp4u_mp4_ctts_atom_t *) atom->pos;

    mp4u_mp4_set_32value(ctts_atom->size, atom_size);
    mp4u_mp4_set_32value(ctts_atom->entries, entries);

    return;
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    entries[4];
} mp4u_mp4_stsc_atom_t;


static int
mp4u_read_stsc_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header, *atom_table, *atom_end;
    uint32_t              entries;
    mp4u_buf_t            *atom, *data;
    mp4u_mp4_stsc_atom_t  *stsc_atom;
    mp4u_trak_t  *trak;

    /* sample-to-chunk atom */

    mp4u_log("mp4 stsc atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    stsc_atom = (mp4u_mp4_stsc_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(stsc_atom, 's', 't', 's', 'c');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_stsc_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stsc atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    entries = mp4u_mp4_get_32value(stsc_atom->entries);

    mp4u_log("sample-to-chunk entries:%uD", entries);

    if (mp4u_mp4_atom_data_size(mp4u_mp4_stsc_atom_t)
        + entries * sizeof(mp4u_mp4_stsc_entry_t) > atom_data_size)
    {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stsc atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    atom_table = atom_header + sizeof(mp4u_mp4_stsc_atom_t);
    atom_end = atom_table + entries * sizeof(mp4u_mp4_stsc_entry_t);

    trak = mp4u_mp4_last_trak(mp4);
    trak->sample_to_chunk_entries = entries;

    atom = &trak->stsc_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_table;

    data = &trak->stsc_data_buf;
    data->pos = atom_table;
    data->last = atom_end;

    trak->out[MP4_STSC_ATOM].buf = atom;
    trak->out[MP4_STSC_DATA].buf = data;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_update_stsc_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    size_t                 atom_size;
    uint32_t               start_sample, entries, chunk, samples, id,
                           next_chunk, n;
    mp4u_buf_t             *atom, *data, *buf;
    mp4u_mp4_stsc_atom_t   *stsc_atom;
    mp4u_mp4_stsc_entry_t  *entry, *first, *end;

    /*
     * mdia.minf.stbl.stsc updating requires trak->start_sample
     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
     * atom which may reside after mdia.minf
     */

    mp4u_log("mp4 stsc atom update");

    data = trak->out[MP4_STSC_DATA].buf;

    if (data == NULL) {
        mp4u_log_error("ERROR:"
                      "no mp4 stsc atoms were found in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    if (trak->sample_to_chunk_entries == 0) {
        mp4u_log_error("ERROR:"
                      "zero number of entries in stsc atom in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    start_sample = (uint32_t) trak->start_sample;
    entries = trak->sample_to_chunk_entries - 1;

    entry = (mp4u_mp4_stsc_entry_t *) data->pos;
    end = (mp4u_mp4_stsc_entry_t *) data->last;

    chunk = mp4u_mp4_get_32value(entry->chunk);
    samples = mp4u_mp4_get_32value(entry->samples);
    id = mp4u_mp4_get_32value(entry->id);
    entry++;

    while (entry < end) {

        next_chunk = mp4u_mp4_get_32value(entry->chunk);

        mp4u_log("start_sample:%"PRId32", chunk:%"PRId32", chunks:%"PRId32", "
                       "samples:%"PRId32", id:%"PRId32"",
                       start_sample, chunk, next_chunk - chunk, samples, id);

        n = (next_chunk - chunk) * samples;

        if (start_sample <= n) {
            goto found;
        }

        start_sample -= n;

        chunk = next_chunk;
        samples = mp4u_mp4_get_32value(entry->samples);
        id = mp4u_mp4_get_32value(entry->id);
        entries--;
        entry++;
    }

    next_chunk = trak->chunks;

    mp4u_log(
                   "start_sample:%uD, chunk:%uD, chunks:%uD, samples:%uD",
                   start_sample, chunk, next_chunk - chunk, samples);

    n = (next_chunk - chunk) * samples;

    if (start_sample > n) {
        mp4u_log_error("ERROR:"
                      "start time is out mp4 stsc chunks in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

found:

    entries++;
    entry--;

    if (samples == 0) {
        mp4u_log_error("ERROR:"
                      "zero number of samples in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    trak->start_chunk = chunk - 1;

    trak->start_chunk += start_sample / samples;
    trak->chunk_samples = start_sample % samples;

    mp4u_log("start chunk:%"PRId32", samples:%"PRId32"",
                   trak->start_chunk, trak->chunk_samples);

    data->pos = (u_char *) entry;
    atom_size = sizeof(mp4u_mp4_stsc_atom_t) + (data->last - data->pos);

    mp4u_mp4_set_32value(entry->chunk, 1);

    if (trak->chunk_samples && next_chunk - trak->start_chunk == 2) {

        /* last chunk in the entry */

        mp4u_mp4_set_32value(entry->samples, samples - trak->chunk_samples);

    } else if (trak->chunk_samples) {

        first = &trak->stsc_chunk_entry;
        mp4u_mp4_set_32value(first->chunk, 1);
        mp4u_mp4_set_32value(first->samples, samples - trak->chunk_samples);
        mp4u_mp4_set_32value(first->id, id);

        buf = &trak->stsc_chunk_buf;
        buf->pos = (u_char *) first;
        buf->last = (u_char *) first + sizeof(mp4u_mp4_stsc_entry_t);

        trak->out[MP4_STSC_CHUNK].buf = buf;

        mp4u_mp4_set_32value(entry->chunk, 2);

        entries++;
        atom_size += sizeof(mp4u_mp4_stsc_entry_t);
    }

    while (++entry < end) {
        chunk = mp4u_mp4_get_32value(entry->chunk);
        chunk -= trak->start_chunk;
        mp4u_mp4_set_32value(entry->chunk, chunk);
    }

    trak->size += atom_size;

    atom = trak->out[MP4_STSC_ATOM].buf;
    stsc_atom = (mp4u_mp4_stsc_atom_t *) atom->pos;

    mp4u_mp4_set_32value(stsc_atom->size, atom_size);
    mp4u_mp4_set_32value(stsc_atom->entries, entries);

    return MP4U_OK;
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    uniform_size[4];
    u_char    entries[4];
} mp4u_mp4_stsz_atom_t;


static int
mp4u_read_stsz_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header, *atom_table, *atom_end;
    size_t                atom_size;
    uint32_t              entries, size;
    mp4u_buf_t            *atom, *data;
    mp4u_mp4_stsz_atom_t  *stsz_atom;
    mp4u_trak_t  *trak;

    /* sample sizes atom */

    mp4u_log("mp4 stsz atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    stsz_atom = (mp4u_mp4_stsz_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(stsz_atom, 's', 't', 's', 'z');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_stsz_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stsz atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    size = mp4u_mp4_get_32value(stsz_atom->uniform_size);
    entries = mp4u_mp4_get_32value(stsz_atom->entries);

    mp4u_log("sample uniform size:%"PRId32", entries:%"PRId32"", size, entries);

    trak = mp4u_mp4_last_trak(mp4);
    trak->sample_sizes_entries = entries;

    atom_table = atom_header + sizeof(mp4u_mp4_stsz_atom_t);

    atom = &trak->stsz_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_table;

    trak->out[MP4_STSZ_ATOM].buf = atom;

    if (size == 0) {
        if (mp4u_mp4_atom_data_size(mp4u_mp4_stsz_atom_t)
            + entries * sizeof(uint32_t) > atom_data_size)
        {
            mp4u_log_error("ERROR:"
                          "\"%s\" mp4 stsz atom too small",
                          mp4->file.name);
            return MP4U_ERROR;
        }

        atom_end = atom_table + entries * sizeof(uint32_t);

        data = &trak->stsz_data_buf;
        data->pos = atom_table;
        data->last = atom_end;

        trak->out[MP4_STSZ_DATA].buf = data;

    } else {
        /* if size != 0 then all samples are the same size */
        /* TODO : chunk samples */
        atom_size = sizeof(mp4u_mp4_atom_header_t) + (size_t) atom_data_size;
        mp4u_mp4_set_32value(atom_header, atom_size);
        trak->size += atom_size;
    }

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_update_stsz_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    size_t                atom_size;
    uint32_t             *pos, *end;
    mp4u_buf_t            *atom, *data;
    mp4u_mp4_stsz_atom_t  *stsz_atom;

    /*
     * mdia.minf.stbl.stsz updating requires trak->start_sample
     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
     * atom which may reside after mdia.minf
     */

    mp4u_log("mp4 stsz atom update");

    data = trak->out[MP4_STSZ_DATA].buf;

    if (data) {
        if (trak->start_sample > trak->sample_sizes_entries) {
            mp4u_log_error("ERROR:"
                          "start time is out mp4 stsz samples in \"%s\"",
                          mp4->file.name);
            return MP4U_ERROR;
        }

        data->pos += trak->start_sample * sizeof(uint32_t);
        end = (uint32_t *) data->pos;

        for (pos = end - trak->chunk_samples; pos < end; pos++) {
            trak->chunk_samples_size += mp4u_mp4_get_32value(pos);
        }

        mp4u_log("chunk samples sizes:%"PRId64"", trak->chunk_samples_size);

        atom_size = sizeof(mp4u_mp4_stsz_atom_t) + (data->last - data->pos);
        trak->size += atom_size;

        atom = trak->out[MP4_STSZ_ATOM].buf;
        stsz_atom = (mp4u_mp4_stsz_atom_t *) atom->pos;

        mp4u_mp4_set_32value(stsz_atom->size, atom_size);
        mp4u_mp4_set_32value(stsz_atom->entries,
                            trak->sample_sizes_entries - trak->start_sample);
    }

    return MP4U_OK;
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    entries[4];
} mp4u_mp4_stco_atom_t;


static int
mp4u_read_stco_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header, *atom_table, *atom_end;
    uint32_t              entries;
    mp4u_buf_t            *atom, *data;
    mp4u_mp4_stco_atom_t  *stco_atom;
    mp4u_trak_t  *trak;

    /* chunk offsets atom */

    mp4u_log("mp4 stco atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    stco_atom = (mp4u_mp4_stco_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(stco_atom, 's', 't', 'c', 'o');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_stco_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stco atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    entries = mp4u_mp4_get_32value(stco_atom->entries);

    mp4u_log("chunks:%uD", entries);

    if (mp4u_mp4_atom_data_size(mp4u_mp4_stco_atom_t)
        + entries * sizeof(uint32_t) > atom_data_size)
    {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 stco atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    atom_table = atom_header + sizeof(mp4u_mp4_stco_atom_t);
    atom_end = atom_table + entries * sizeof(uint32_t);

    trak = mp4u_mp4_last_trak(mp4);
    trak->chunks = entries;

    atom = &trak->stco_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_table;

    data = &trak->stco_data_buf;
    data->pos = atom_table;
    data->last = atom_end;

    trak->out[MP4_STCO_ATOM].buf = atom;
    trak->out[MP4_STCO_DATA].buf = data;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_update_stco_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    size_t                atom_size;
    mp4u_buf_t            *atom, *data;
    mp4u_mp4_stco_atom_t  *stco_atom;

    /*
     * mdia.minf.stbl.stco updating requires trak->start_chunk
     * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd
     * atom which may reside after mdia.minf
     */

    mp4u_log("mp4 stco atom update");

    data = trak->out[MP4_STCO_DATA].buf;

    if (data == NULL) {
        mp4u_log_error("ERROR:"
                      "no mp4 stco atoms were found in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    if (trak->start_chunk > trak->chunks) {
        mp4u_log_error("ERROR:"
                      "start time is out mp4 stco chunks in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    data->pos += trak->start_chunk * sizeof(uint32_t);
    atom_size = sizeof(mp4u_mp4_stco_atom_t) + (data->last - data->pos);
    trak->size += atom_size;

    trak->start_offset = mp4u_mp4_get_32value(data->pos);
    trak->start_offset += trak->chunk_samples_size;
    mp4u_mp4_set_32value(data->pos, trak->start_offset);

    mp4u_log("start chunk offset:%lld", (long long )trak->start_offset);

    atom = trak->out[MP4_STCO_ATOM].buf;
    stco_atom = (mp4u_mp4_stco_atom_t *) atom->pos;

    mp4u_mp4_set_32value(stco_atom->size, atom_size);
    mp4u_mp4_set_32value(stco_atom->entries, trak->chunks - trak->start_chunk);

    return MP4U_OK;
}


static void
mp4u_adjust_stco_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak, int32_t adjustment)
{
    uint32_t    offset, *entry, *end;
    mp4u_buf_t  *data;

    /*
     * moov.trak.mdia.minf.stbl.stco adjustment requires
     * minimal start offset of all traks and new moov atom size
     */

    mp4u_log("mp4 stco atom adjustment");

    data = trak->out[MP4_STCO_DATA].buf;
    entry = (uint32_t *) data->pos;
    end = (uint32_t *) data->last;

    while (entry < end) {
        offset = mp4u_mp4_get_32value(entry);
        offset += adjustment;
        mp4u_mp4_set_32value(entry, offset);
        entry++;
    }
}


typedef struct {
    u_char    size[4];
    u_char    name[4];
    u_char    version[1];
    u_char    flags[3];
    u_char    entries[4];
} mp4u_mp4_co64_atom_t;


static int
mp4u_read_co64_atom(mp4u_file_t *mp4, uint64_t atom_data_size)
{
    u_char               *atom_header, *atom_table, *atom_end;
    uint32_t              entries;
    mp4u_buf_t            *atom, *data;
    mp4u_mp4_co64_atom_t  *co64_atom;
    mp4u_trak_t  *trak;

    /* chunk offsets atom */

    mp4u_log("mp4 co64 atom");

    atom_header = mp4u_mp4_atom_header(mp4);
    co64_atom = (mp4u_mp4_co64_atom_t *) atom_header;
    mp4u_mp4_set_atom_name(co64_atom, 'c', 'o', '6', '4');

    if (mp4u_mp4_atom_data_size(mp4u_mp4_co64_atom_t) > atom_data_size) {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 co64 atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    entries = mp4u_mp4_get_32value(co64_atom->entries);

    mp4u_log("chunks:%uD", entries);

    if (mp4u_mp4_atom_data_size(mp4u_mp4_co64_atom_t)
        + entries * sizeof(uint64_t) > atom_data_size)
    {
        mp4u_log_error("ERROR:"
                      "\"%s\" mp4 co64 atom too small", mp4->file.name);
        return MP4U_ERROR;
    }

    atom_table = atom_header + sizeof(mp4u_mp4_co64_atom_t);
    atom_end = atom_table + entries * sizeof(uint64_t);

    trak = mp4u_mp4_last_trak(mp4);
    trak->chunks = entries;

    atom = &trak->co64_atom_buf;
    atom->pos = atom_header;
    atom->last = atom_table;

    data = &trak->co64_data_buf;
    data->pos = atom_table;
    data->last = atom_end;

    trak->out[MP4_CO64_ATOM].buf = atom;
    trak->out[MP4_CO64_DATA].buf = data;

    mp4u_mp4_atom_next(mp4, atom_data_size);

    return MP4U_OK;
}


static int
mp4u_update_co64_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak)
{
    size_t                atom_size;
    mp4u_buf_t            *atom, *data;
    mp4u_mp4_co64_atom_t  *co64_atom;

    /*
     * mdia.minf.stbl.co64 updating requires trak->start_chunk
     * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd
     * atom which may reside after mdia.minf
     */

    mp4u_log("mp4 co64 atom update");

    data = trak->out[MP4_CO64_DATA].buf;

    if (data == NULL) {
        mp4u_log_error("ERROR:"
                      "no mp4 co64 atoms were found in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    if (trak->start_chunk > trak->chunks) {
        mp4u_log_error("ERROR:"
                      "start time is out mp4 co64 chunks in \"%s\"",
                      mp4->file.name);
        return MP4U_ERROR;
    }

    data->pos += trak->start_chunk * sizeof(uint64_t);
    atom_size = sizeof(mp4u_mp4_co64_atom_t) + (data->last - data->pos);
    trak->size += atom_size;

    trak->start_offset = mp4u_mp4_get_64value(data->pos);
    trak->start_offset += trak->chunk_samples_size;
    mp4u_mp4_set_64value(data->pos, trak->start_offset);

    mp4u_log("start chunk offset:%lld", (long long)trak->start_offset);

    atom = trak->out[MP4_CO64_ATOM].buf;
    co64_atom = (mp4u_mp4_co64_atom_t *) atom->pos;

    mp4u_mp4_set_32value(co64_atom->size, atom_size);
    mp4u_mp4_set_32value(co64_atom->entries, trak->chunks - trak->start_chunk);

    return MP4U_OK;
}


static void
mp4u_adjust_co64_atom(mp4u_file_t *mp4,
    mp4u_trak_t *trak, off_t adjustment)
{
    uint64_t    offset, *entry, *end;
    mp4u_buf_t  *data;

    /*
     * moov.trak.mdia.minf.stbl.co64 adjustment requires
     * minimal start offset of all traks and new moov atom size
     */

    mp4u_log("mp4 co64 atom adjustment");

    data = trak->out[MP4_CO64_DATA].buf;
    entry = (uint64_t *) data->pos;
    end = (uint64_t *) data->last;

    while (entry < end) {
        offset = mp4u_mp4_get_64value(entry);
        offset += adjustment;
        mp4u_mp4_set_64value(entry, offset);
        entry++;
    }
}


static int 
mp4u_parse_spspps(mp4u_spspps_t  *spspps, u_char *data, uint32_t len)
{
    u_char   *p = data;
    int       i;
    if (data == NULL || spspps == NULL) return MP4U_ERROR;
    memset(spspps, 0, sizeof(mp4u_spspps_t));
     
    spspps = (mp4u_spspps_t*)data;

    spspps->naullen  =  (spspps->naullen & 3) + 1;
    spspps->spscount =  (spspps->spscount & 0x1F);
    for(i=0; i<spspps->spscount; i++){
        spspps->spslen   = mp4u_mp4_get_16value(p+6);
        spspps->sps      = p + 8;
        p                = spspps->sps + spspps->spslen;
    }

    spspps->ppscount = (unsigned)p[0];
    for(i=0; i<spspps->ppscount; i++){
        spspps->ppslen   = mp4u_mp4_get_16value(p+1); 
        spspps->pps      = p + 3;
    }
   
    int n =0;
    for(i=1; i<10; i++){
        n = i*4;
        mp4u_log("spspps naullen:%d spscount:%d spslen:%d ppscount:%d ppslen:%d sps:%.2x|%.2x|%.2x|%.2x pps:%c",
              spspps->naullen, spspps->spscount, spspps->spslen, spspps->ppscount, spspps->ppslen,
              (unsigned int)data[n],(unsigned int)data[n+1],(unsigned int)data[n+2],(unsigned int)data[n+3], spspps->pps[0]);
    }

  
    return MP4U_OK;
}


#ifdef TEST_MP4
int 
main(int argc, char *const *argv)
{
    mp4u_file_t  mp4;
    int   ret, i;
    mp4u_trak_t *trak;

    if ((ret=mp4u_init(&mp4, argv[1])) != MP4U_OK){
        mp4u_log_error("mp4u_init error:%d", ret);
        return ret; 
    }
   
    ret = mp4u_process(&mp4);
    mp4u_log("process result:%d", ret);

 
    u_char *spspps;
    trak = mp4.trak.elts;

    for(i=0; i<mp4.trak.nelts; i++){
        if (trak[i].avcC_size > 0){
            spspps = trak[i].avcC_data_buf.pos;
            mp4u_log("avcC_size:%d spspps:%p", trak[i].avcC_size, spspps);
        } 
    }

    mp4u_free(&mp4);
    
    return ret; 
}
#endif
